/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.3.1): collapse.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

import $ from 'jquery'
import Util from './util'

/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */

const NAME = 'collapse'
const VERSION = '4.3.1'
const DATA_KEY = 'bs.collapse'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const JQUERY_NO_CONFLICT = $.fn[NAME]

const Default = {
    toggle: true,
    parent: ''
}

const DefaultType = {
    toggle: 'boolean',
    parent: '(string|element)'
}

const Event = {
    SHOW: `show${EVENT_KEY}`,
    SHOWN: `shown${EVENT_KEY}`,
    HIDE: `hide${EVENT_KEY}`,
    HIDDEN: `hidden${EVENT_KEY}`,
    CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}`
}

const ClassName = {
    SHOW: 'show',
    COLLAPSE: 'collapse',
    COLLAPSING: 'collapsing',
    COLLAPSED: 'collapsed'
}

const Dimension = {
    WIDTH: 'width',
    HEIGHT: 'height'
}

const Selector = {
    ACTIVES: '.show, .collapsing',
    DATA_TOGGLE: '[data-toggle="collapse"]'
}

/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class Collapse {
    constructor(element, config) {
        this._isTransitioning = false
        this._element = element
        this._config = this._getConfig(config)
        this._triggerArray = [].slice.call(document.querySelectorAll(
            `[data-toggle="collapse"][href="#${element.id}"],` +
            `[data-toggle="collapse"][data-target="#${element.id}"]`
        ))

        const toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE))
        for (let i = 0, len = toggleList.length; i < len; i++) {
            const elem = toggleList[i]
            const selector = Util.getSelectorFromElement(elem)
            const filterElement = [].slice.call(document.querySelectorAll(selector))
                .filter((foundElem) => foundElem === element)

            if (selector !== null && filterElement.length > 0) {
                this._selector = selector
                this._triggerArray.push(elem)
            }
        }

        this._parent = this._config.parent ? this._getParent() : null

        if (!this._config.parent) {
            this._addAriaAndCollapsedClass(this._element, this._triggerArray)
        }

        if (this._config.toggle) {
            this.toggle()
        }
    }

    // Getters

    static get VERSION() {
        return VERSION
    }

    static get Default() {
        return Default
    }

    // Public

    toggle() {
        if ($(this._element).hasClass(ClassName.SHOW)) {
            this.hide()
        } else {
            this.show()
        }
    }

    show() {
        if (this._isTransitioning ||
            $(this._element).hasClass(ClassName.SHOW)) {
            return
        }

        let actives
        let activesData

        if (this._parent) {
            actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES))
                .filter((elem) => {
                    if (typeof this._config.parent === 'string') {
                        return elem.getAttribute('data-parent') === this._config.parent
                    }

                    return elem.classList.contains(ClassName.COLLAPSE)
                })

            if (actives.length === 0) {
                actives = null
            }
        }

        if (actives) {
            activesData = $(actives).not(this._selector).data(DATA_KEY)
            if (activesData && activesData._isTransitioning) {
                return
            }
        }

        const startEvent = $.Event(Event.SHOW)
        $(this._element).trigger(startEvent)
        if (startEvent.isDefaultPrevented()) {
            return
        }

        if (actives) {
            Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')
            if (!activesData) {
                $(actives).data(DATA_KEY, null)
            }
        }

        const dimension = this._getDimension()

        $(this._element)
            .removeClass(ClassName.COLLAPSE)
            .addClass(ClassName.COLLAPSING)

        this._element.style[dimension] = 0

        if (this._triggerArray.length) {
            $(this._triggerArray)
                .removeClass(ClassName.COLLAPSED)
                .attr('aria-expanded', true)
        }

        this.setTransitioning(true)

        const complete = () => {
            $(this._element)
                .removeClass(ClassName.COLLAPSING)
                .addClass(ClassName.COLLAPSE)
                .addClass(ClassName.SHOW)

            this._element.style[dimension] = ''

            this.setTransitioning(false)

            $(this._element).trigger(Event.SHOWN)
        }

        const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
        const scrollSize = `scroll${capitalizedDimension}`
        const transitionDuration = Util.getTransitionDurationFromElement(this._element)

        $(this._element)
            .one(Util.TRANSITION_END, complete)
            .emulateTransitionEnd(transitionDuration)

        this._element.style[dimension] = `${this._element[scrollSize]}px`
    }

    hide() {
        if (this._isTransitioning ||
            !$(this._element).hasClass(ClassName.SHOW)) {
            return
        }

        const startEvent = $.Event(Event.HIDE)
        $(this._element).trigger(startEvent)
        if (startEvent.isDefaultPrevented()) {
            return
        }

        const dimension = this._getDimension()

        this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`

        Util.reflow(this._element)

        $(this._element)
            .addClass(ClassName.COLLAPSING)
            .removeClass(ClassName.COLLAPSE)
            .removeClass(ClassName.SHOW)

        const triggerArrayLength = this._triggerArray.length
        if (triggerArrayLength > 0) {
            for (let i = 0; i < triggerArrayLength; i++) {
                const trigger = this._triggerArray[i]
                const selector = Util.getSelectorFromElement(trigger)

                if (selector !== null) {
                    const $elem = $([].slice.call(document.querySelectorAll(selector)))
                    if (!$elem.hasClass(ClassName.SHOW)) {
                        $(trigger).addClass(ClassName.COLLAPSED)
                            .attr('aria-expanded', false)
                    }
                }
            }
        }

        this.setTransitioning(true)

        const complete = () => {
            this.setTransitioning(false)
            $(this._element)
                .removeClass(ClassName.COLLAPSING)
                .addClass(ClassName.COLLAPSE)
                .trigger(Event.HIDDEN)
        }

        this._element.style[dimension] = ''
        const transitionDuration = Util.getTransitionDurationFromElement(this._element)

        $(this._element)
            .one(Util.TRANSITION_END, complete)
            .emulateTransitionEnd(transitionDuration)
    }

    setTransitioning(isTransitioning) {
        this._isTransitioning = isTransitioning
    }

    dispose() {
        $.removeData(this._element, DATA_KEY)

        this._config = null
        this._parent = null
        this._element = null
        this._triggerArray = null
        this._isTransitioning = null
    }

    // Private

    _getConfig(config) {
        config = {
            ...Default,
            ...config
        }
        config.toggle = Boolean(config.toggle) // Coerce string values
        Util.typeCheckConfig(NAME, config, DefaultType)
        return config
    }

    _getDimension() {
        const hasWidth = $(this._element).hasClass(Dimension.WIDTH)
        return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT
    }

    _getParent() {
        let parent

        if (Util.isElement(this._config.parent)) {
            parent = this._config.parent

            // It's a jQuery object
            if (typeof this._config.parent.jquery !== 'undefined') {
                parent = this._config.parent[0]
            }
        } else {
            parent = document.querySelector(this._config.parent)
        }

        const selector =
            `[data-toggle="collapse"][data-parent="${this._config.parent}"]`

        const children = [].slice.call(parent.querySelectorAll(selector))
        $(children).each((i, element) => {
            this._addAriaAndCollapsedClass(
                Collapse._getTargetFromElement(element), [element]
            )
        })

        return parent
    }

    _addAriaAndCollapsedClass(element, triggerArray) {
        const isOpen = $(element).hasClass(ClassName.SHOW)

        if (triggerArray.length) {
            $(triggerArray)
                .toggleClass(ClassName.COLLAPSED, !isOpen)
                .attr('aria-expanded', isOpen)
        }
    }

    // Static

    static _getTargetFromElement(element) {
        const selector = Util.getSelectorFromElement(element)
        return selector ? document.querySelector(selector) : null
    }

    static _jQueryInterface(config) {
        return this.each(function() {
            const $this = $(this)
            let data = $this.data(DATA_KEY)
            const _config = {
                ...Default,
                ...$this.data(),
                ...typeof config === 'object' && config ? config : {}
            }

            if (!data && _config.toggle && /show|hide/.test(config)) {
                _config.toggle = false
            }

            if (!data) {
                data = new Collapse(this, _config)
                $this.data(DATA_KEY, data)
            }

            if (typeof config === 'string') {
                if (typeof data[config] === 'undefined') {
                    throw new TypeError(`No method named "${config}"`)
                }
                data[config]()
            }
        })
    }
}

/**
 * ------------------------------------------------------------------------
 * Data Api implementation
 * ------------------------------------------------------------------------
 */

$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function(event) {
    // preventDefault only for <a> elements (which change the URL) not inside the collapsible element
    if (event.currentTarget.tagName === 'A') {
        event.preventDefault()
    }

    const $trigger = $(this)
    const selector = Util.getSelectorFromElement(this)
    const selectors = [].slice.call(document.querySelectorAll(selector))

    $(selectors).each(function() {
        const $target = $(this)
        const data = $target.data(DATA_KEY)
        const config = data ? 'toggle' : $trigger.data()
        Collapse._jQueryInterface.call($target, config)
    })
})

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */

$.fn[NAME] = Collapse._jQueryInterface
$.fn[NAME].Constructor = Collapse
$.fn[NAME].noConflict = () => {
    $.fn[NAME] = JQUERY_NO_CONFLICT
    return Collapse._jQueryInterface
}

export default Collapse